
#ifndef AFTERMATH_PROBABILITY_EMPIRICAL_MEASURE_HELPER_HPP_INCLUDED
#define AFTERMATH_PROBABILITY_EMPIRICAL_MEASURE_HELPER_HPP_INCLUDED

#include "../type_concepts.hpp"

#include <cstddef>
#include <cstdint>
#include <map>
#include <type_traits>
#include <unordered_map>

namespace aftermath
{
    namespace probability
    {
        namespace detail
        {
            template <typename t_key_type, typename t_count_type>
            struct dictionary;

            template <typename t_key_type, typename t_count_type>
            using dictionary_t = typename dictionary<t_key_type, t_count_type>::type;

            template <typename t_key_type, typename t_count_type>
            struct dictionary
            {
                static const bool is_ordered = type_impl::has_less<t_key_type>::value;

                typedef t_key_type key_type;
                typedef t_count_type count_type;
                typedef typename type_impl::type_switch<is_ordered,
                    std::map<key_type, count_type>,
                    std::unordered_map<key_type, count_type>>::type type;

                template <typename t_dictionary_type>
                static void merge(t_dictionary_type& target_data, const t_dictionary_type& source_data)
                {
                    for (const auto& item : source_data)
                    {
                        auto it = target_data.find(item.first);
                        if (it != target_data.end())
                        {
                            auto height = (it->second) + item.second;
                            it->second = height;
                        }
                        else target_data.insert(item);
                    }
                }

                template <typename t_target_dictionary_type, typename t_source_dictionary_type, typename t_transformer_type>
                static void merge(t_target_dictionary_type& target_data, const t_source_dictionary_type& source_data, const t_transformer_type& transformer)
                {
                    for (const auto& item : source_data)
                    {
                        auto value = transformer(item.first);
                        auto it = target_data.find(value);
                        if (it != target_data.end())
                        {
                            auto height = (it->second) + item.second;
                            it->second = height;
                        }
                        else target_data.insert(std::make_pair(value, item.second));
                    }
                }

                template <typename t_dictionary_type>
                static count_type find_or_default(const t_dictionary_type& data, const typename t_dictionary_type::key_type& key, const typename t_dictionary_type::mapped_type& default_value)
                {
                    auto it = data.find(key);
                    return it != data.end() ? it->second : default_value;
                }

                template <typename t_dictionary_type>
                static count_type find_or_default(const t_dictionary_type& data, const typename t_dictionary_type::key_type& key, typename t_dictionary_type::mapped_type&& default_value)
                {
                    auto it = data.find(key);
                    return it != data.end() ? it->second : default_value;
                }

                template <typename t_dictionary_type>
                static void copy(const t_dictionary_type& data, typename t_dictionary_type::key_type* p_keys, typename t_dictionary_type::mapped_type* p_values)
                {
                    std::size_t index = 0;
                    for (const auto& item : data)
                    {
                        p_keys[index] = item.first;
                        p_values[index] = item.second;
                        index++;
                    }
                }

                template <typename t_dictionary_type>
                static void copy(const t_dictionary_type& data, typename t_dictionary_type::key_type* p_keys, double* p_values, double norm)
                {
                    std::size_t index = 0;
                    for (const auto& item : data)
                    {
                        p_keys[index] = item.first;
                        p_values[index] = item.second / norm;
                        index++;
                    }
                }
            };
        }
    }
}

#endif // AFTERMATH_PROBABILITY_EMPIRICAL_MEASURE_HELPER_HPP_INCLUDED
